using System;
using System.Collections.Generic;
using System.Text;

using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Collections;


using Athena.Objects;
using System.ComponentModel;

namespace Athena
{
    public class VisualCanvas : Panel 
    {
        public Cursor MouseCursor
        {
            get { return this.Cursor; }
            set { this.Cursor = value; }
        }

        public void ExposedResetMouseEventArgs()
        {
            base.ResetMouseEventArgs();
        } 
        VScrollBar _oScrollV = null;
        HScrollBar _oScrollH = null;

        /// <summary>
        /// Binds the Canvas to a vertical and horizontal scroll bar. 
        /// </summary>
        /// <param name="oVScroll"></param>
        /// <param name="oHScroll"></param>
        public void BindScrollbars(VScrollBar oVScroll, HScrollBar oHScroll)
        {
            _oScrollV = oVScroll;
            _oScrollH = oHScroll;
            _oScrollH.Scroll += new ScrollEventHandler(OnHorizontalScoll);
            _oScrollV.Scroll += new ScrollEventHandler(OnVerticalScroll);
            
            UpdateScroll();
        }

        public VisualCanvas()
        {
            InitializeComponent(); 

            SetStyle(ControlStyles.AllPaintingInWmPaint |
                ControlStyles.OptimizedDoubleBuffer |
                ControlStyles.ResizeRedraw |
                ControlStyles.UserPaint, true);
            
        }

        #region // Designer code

        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // VisualCanvas
            // 
            this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
            this.BackColor = System.Drawing.Color.White;
            this.TabStop = true;
            this.Paint += new System.Windows.Forms.PaintEventHandler(this.VisualCanvas_Paint);
            this.ResumeLayout(false);

        }
        #endregion

        /// <summary>
        /// Overridden in order to keep the panel focussed even when moving
        /// </summary>
        /// <param name="keyData"></param>
        /// <returns></returns>
        protected override bool ProcessDialogKey(Keys keyData)
        {
            //System.Diagnostics.Debug.WriteLine(keyData.ToString());
            switch (keyData)
            {
                case Keys.Up:
                case Keys.Down:
                case Keys.Left:
                case Keys.Right:
                case Keys.Up | Keys.Shift:
                case Keys.Down | Keys.Shift:
                case Keys.Left | Keys.Shift:
                case Keys.Right | Keys.Shift:
                case Keys.Tab:
                case Keys.ShiftKey:
                case Keys.ShiftKey | Keys.Shift:
                case Keys.Shift:
                case Keys.Shift & Keys.Tab:
                case Keys.Tab | Keys.Shift:
                case Keys.Back | Keys.Shift:                    
                case Keys.A | Keys.Control:
                case Keys.Escape:
                    return false;
                default:
                    break;
            }
            return base.ProcessDialogKey(keyData);
        }

    
        /// <summary>
        /// Zooms in by factor 2f
        /// </summary>
        public void ZoomIn()
        {
            ScaleFactor *= 2F;
            Invalidate();
        }

        /// <summary>
        /// Zooms out by factor 2f
        /// </summary>
        public void ZoomOut()
        {
            ScaleFactor /= 2f;
            Invalidate();
        }


        public class MinMax
        {
            private float _oMinimum;

            /// <summary>
            /// Gets / Sets the Minimum
            /// </summary>
            public float Minimum
            {
                get { return _oMinimum; }
                set { _oMinimum = value; }
            }
            private float _oMaximum;

            /// <summary>
            /// Gets / Sets the Maximum
            /// </summary>
            public float Maximum
            {
                get { return _oMaximum; }
                set { _oMaximum = value; }
            }
            
            /// <summary>
            /// Initializes a new instance of the MinMax class.
            /// </summary>
            /// <param name="oMinimum"></param>
            /// <param name="oMaximum"></param>
            public MinMax(float oMinimum, float oMaximum)
            {
                _oMinimum = oMinimum;
                _oMaximum = oMaximum;
            }

            /// <summary>
            /// Initializes a new instance of the MinMax class.
            /// </summary>
            /// <param name="oMinimum"></param>
            /// <param name="oMaximum"></param>
            public MinMax(int oMinimum, int oMaximum)
            {
                _oMinimum = (float)oMinimum;
                _oMaximum = (float)oMaximum;
            }

            /// <summary>
            /// Returns a custom string representing the object
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                return String.Format("Minimum={0}, Maximum={1}", Minimum, Maximum);
            }
        }

        PointF _oOffset = new PointF();

        [TypeConverter(typeof(PointFConverter)), Description("The Offset for all rendered elements"),Category("VisualCanvas")]
        public PointF Offset
        {
            get
            {
                return new PointF(-_oOffset.X, -_oOffset.Y);
            }
            set
            {
                if (_oScrollH == null || _oScrollV == null) return;


                PointF oValue = value;

                // make sure we are staying in the correct ranges ... 
                if (value.X < _oScrollH.Minimum)      oValue.X = _oScrollH.Minimum;
                else if (value.X > _oScrollH.Maximum) oValue.X = _oScrollH.Maximum; 
                    
                if (value.Y < _oScrollV.Minimum)      oValue.Y = _oScrollV.Minimum;
                else if (value.Y > _oScrollV.Maximum) oValue.Y = _oScrollV.Maximum;

                // assigning the values ...
                _oOffset = new PointF(-oValue.X, -oValue.Y);

                // updating the canvas
                UpdateScroll();
                Invalidate();
            }
        }


        void Translate(float dX, float dY)
        {
            Translate(new PointF(dX, dY));
        }
        void Translate(PointF oOffset)
        {
            _oOffset = oOffset;
        }
        void TranslateRelative(float dX, float dY)
        {            
            _oOffset.X += dX;
            _oOffset.Y += dY;
        }

        public void Reset()
        {
            _oOffset = new PointF();
            ScaleFactor = 1f;
            UpdateScroll();
            Invalidate();
        }

        public void ScrollUp()
        {
            if (_oScrollV == null) return;
            if (_oScrollV.Value - _oScrollV.SmallChange < _oScrollV.Minimum)
            {
                Offset = new PointF(Offset.X, _oScrollV.Minimum);
            }
            else
            {
                TranslateRelative(0, _oScrollV.SmallChange);
            }
            UpdateScroll();
            Invalidate();
        }

        public void ScrollDown()
        {
            if (_oScrollV == null) return;
            if (_oScrollV.Value + _oScrollV.SmallChange > _oScrollV.Maximum)
            {
                Offset = new PointF(Offset.X, _oScrollV.Maximum);
            }
            else
            {
                TranslateRelative(0,-_oScrollV.SmallChange);
            }
            UpdateScroll();
            Invalidate();            
        }

        void OnVerticalScroll(object sender, ScrollEventArgs e)
        {
            Offset = new PointF(Offset.X, e.NewValue);
        }

        void OnHorizontalScoll(object sender, ScrollEventArgs e)
        {
            Offset = new PointF(e.NewValue, Offset.Y);
        }

        private void UpdateScroll()
        {

            if (_oScrollH == null || _oScrollV == null) return;

            Rectangle clientRectangle = ClientRectangle;
            clientRectangle.Width -= _oScrollV.Width;
            clientRectangle.Height -= _oScrollH.Height;

            SizeF oMaxSize = new SizeF(
                (clientRectangle.Width) / _Scale,
                (clientRectangle.Height) / _Scale);

            SizeF oModelDimensions = new SizeF(CurrentModel.ModelDimensions.Width,
                CurrentModel.ModelDimensions.Height);

            SizeF oRelativeSize = new SizeF(oModelDimensions.Width / oMaxSize.Width,
                oModelDimensions.Height / oMaxSize.Height);


            // set up maxima of the scroll bars
            if (!(oRelativeSize.Width - 1f < 0))
                _oScrollH.Maximum = (int)Math.Ceiling((oRelativeSize.Width - 1f) * clientRectangle.Width) + _oScrollV.Width;
            else
                _oScrollH.Maximum = 0;
            if (!(oRelativeSize.Height - 1f < 0))
                _oScrollV.Maximum = (int)Math.Ceiling((oRelativeSize.Height - 1f) * clientRectangle.Height) + _oScrollH.Height;
            else
                _oScrollV.Maximum = 0;

            // set up the SmallChanges
            _oScrollH.SmallChange = (int)Math.Ceiling(_oScrollH.Maximum / 100f * Math.Max(1f, _Scale));
            _oScrollV.SmallChange = (int)Math.Ceiling(_oScrollV.Maximum / 100f * Math.Max(1f, _Scale));

            int val = (int)Math.Ceiling(Offset.X);
            if (val <= _oScrollH.Minimum) val = _oScrollH.Minimum;
            if (val >= _oScrollH.Maximum) val = _oScrollH.Maximum;
            if (val >= _oScrollH.Minimum && val <= _oScrollH.Maximum)
                _oScrollH.Value = val;

            val = (int)Math.Ceiling(Offset.Y);
            if (val <= _oScrollV.Minimum) val = _oScrollV.Minimum;
            if (val >= _oScrollV.Maximum) val = _oScrollV.Maximum;
            if (val >= _oScrollV.Minimum && val <= _oScrollV.Maximum)
                _oScrollV.Value = val;


            if (ScrollSize.Width == 0)
                _oScrollH.Visible = false;
            else
                _oScrollH.Visible = true;
            if (ScrollSize.Height == 0)
                _oScrollV.Visible = false;
            else
                _oScrollV.Visible = true;

            _oScrollV.Invalidate();
            _oScrollH.Invalidate();
        }

        [Description("Current slider Position..."), Category("VisualCanvas")]
        public Point SliderPosition
        {
            get 
            {
                try
                {
                    return new Point(_oScrollH.Value, _oScrollV.Value); 
                }
                catch (Exception)
                {

                    return new Point();
                }
                
            }
            set 
            {
                if (_oScrollV == null || _oScrollH == null) return;
                OnVerticalScroll(this, new ScrollEventArgs(
                    ScrollEventType.EndScroll, _oScrollV.Value, value.Y));
                OnHorizontalScoll(this, new ScrollEventArgs(
                    ScrollEventType.EndScroll, _oScrollH.Value, value.X)); 
            }
        }

        private float _Scale = 1F;
        [Description("The scale factor all Elements will be scaled by"), Category("VisualCanvas")]
        public float ScaleFactor
        {
            get
            {
                return _Scale;
            }
            set
            {
                if (value == 0 || value==_Scale) return;

                _oOffset.X /= _Scale;
                _oOffset.Y /= _Scale;

                _Scale = value;

                _oOffset.X *= _Scale;
                _oOffset.Y *= _Scale;

                UpdateScroll();
                Invalidate();
                UpdateZoom();
                Utility.SetMessage(String.Format("Scale = {0}%", (int)Math.Round(_Scale * 100f)));
            }
        }

        [Description("Enables / Disables the scroll bars"), Category("VisualCanvas")]
        public bool CanPan
        {
            get
            {
                if (_oScrollH == null || _oScrollV == null) return false;
                return _oScrollH.Visible || _oScrollV.Visible;
            }
            set
            {
                if (_oScrollH == null || _oScrollV == null) return;
                _oScrollH.Visible = value;
                _oScrollV.Visible = value;
            }
        }

        [Description("The maximum extend of the scroll bars"), Category("VisualCanvas")]
        public Size ScrollSize
        {
            get
            {
                if (_oScrollH == null || _oScrollV == null) return Size.Empty;
                return new Size(_oScrollH.Maximum, _oScrollV.Maximum);
            }
            set
            {
                if (_oScrollH == null || _oScrollV == null) return;
                _oScrollH.Maximum = value.Width;
                _oScrollV.Maximum = value.Height;
            }
        }

        private void VisualCanvas_Paint(object sender, PaintEventArgs e)
        {
            Draw(e);
        }

        public PointF GetTransformedPoint(PointF oPoint)
        {
            Matrix oNew = Transform.Clone();
            oNew.Invert();
            PointF[] oTemp = new PointF[] { oPoint };
            oNew.TransformPoints(oTemp);
            return oTemp[0];
        }

        private RectangleF _SelectRectangleF = new RectangleF();

        [Description("The selection Rectangle"), Category("VisualCanvas"), TypeConverter(typeof(RectangleFConverter))]
        public RectangleF SelectRectangle
        {
            get { return _SelectRectangleF; }
            set { _SelectRectangleF = value; }
        }


        Model CurrentModel = Program.Model;

        public void FitToView(List<IDrawable> oItems)
        {
            if (oItems == null || oItems.Count == 0) return;
            
            RectangleF oBounds = Utility.GetBounds(oItems);

            oBounds.Inflate(16F, 16F);

            ScaleFactor = Math.Min(
                (ClientRectangle.Width - _oScrollV.Width) / oBounds.Width,
                (ClientRectangle.Height - _oScrollH.Height) / oBounds.Height);

            //float paddingX = (ClientRectangle.Width - oBounds.Width * _Scale) / _Scale / 4F;
            //float paddingY = (ClientRectangle.Height - oBounds.Height * _Scale) / _Scale / 4F;
            Offset = new PointF(oBounds.Location.X * _Scale + 0, oBounds.Location.Y * _Scale + 0);


            UpdateScroll();
            Invalidate();
        }

        public void FitToView()
        {
            FitToView(CurrentModel.Drawables);
        }

        [Browsable(false)]
        public Matrix Transform
        {
            get 
            {
                Matrix oTransform = new Matrix();
                oTransform.Scale(ScaleFactor, ScaleFactor, MatrixOrder.Prepend);
                oTransform.Translate(-Offset.X , -Offset.Y , MatrixOrder.Append);
                
                return oTransform;
            }
        }

        //static long nCount = 0;
        public void Draw(PaintEventArgs e)
        {
            //DateTime oStart = DateTime.Now;
            e.Graphics.Transform = Transform;
            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
            e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;

            //CurrentModel.Background.Draw(e.Graphics, CurrentModel.Bounds);
            CurrentModel.Background.Draw(e.Graphics, 
                new RectangleF(
                0f,
                0f,
                CurrentModel.ModelDimensions.Width, 
                CurrentModel.ModelDimensions.Height));
            //e.Graphics.Clear(Color.White);
            
            ToolManager.Instance.PreRender(e.Graphics);
            
            RenderCanvas(e.Graphics, true);
            
            ToolManager.Instance.PostRender(e.Graphics);
            
            //nCount++;
            //TimeSpan oSpan = TimeSpan.FromTicks(DateTime.Now.Ticks - oStart.Ticks);
            //System.Diagnostics.Debug.WriteLine("Draw: " + nCount + " took : " + oSpan.TotalMilliseconds + "ms (For " + CurrentModel.Drawables.Count + " items)");

        }

        public void RenderCanvas(Graphics g, bool renderSelection)
        {
            RenderCanvas(g, CurrentModel.Drawables, renderSelection);
        }

        public void RenderCanvas(Graphics g, List<IDrawable> oItemList, bool renderSelection)
        {
            try
            {
                foreach (IDrawable d in oItemList)
                {
                    if (d.IsVisible) d.Draw(g);
                }

                if (renderSelection)
                {
                    if (_SelectRectangleF.Width == 0 || _SelectRectangleF.Height == 0) return;
                    Color oColor1 = Color.FromArgb(128, Color.White);
                    Color oColor2 = Color.FromArgb(128, Color.LightBlue);

                    LinearGradientBrush oBrush = new LinearGradientBrush(_SelectRectangleF, oColor1, oColor2, 45F);

                    Pen oPen = new Pen(oColor2, 2.0f);
                    g.DrawRectangle(oPen, _SelectRectangleF.Location.X, _SelectRectangleF.Location.Y, _SelectRectangleF.Size.Width, _SelectRectangleF.Size.Height);
                    g.FillRectangle(oBrush, _SelectRectangleF);
                    oBrush.Dispose();
                    oPen.Dispose();
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.Message);
            }            
        }

        private void UpdateZoom()
        {
            MainForm oForm = Program.Form;
            if (oForm == null || oForm.InvokeRequired)
            {
                return;
            }

            oForm.toolStripZoom.Text = string.Format("{0}%", ((int)Math.Ceiling(_Scale * 100f)));
        }
    }
}
